home *** CD-ROM | disk | FTP | other *** search
Text File | 1992-11-23 | 8.8 KB | 308 lines | [ttro/ttxt] |
- Error Handling in AppleScript
- =============================
- Warren Harris 11/22/92
-
- AppleScript has built-in facilities for handling errors. Errors (a.k.a.
- exceptions) raised during the processing of a script cause non-local transfer
- of control to an "error handler" occuring in the dynamic call chain of the
- computation. (Huh?) This means that you never have to write code that checks
- for error numbers as return values.
-
- Quick, and example, before they fall asleep...
-
- Perhaps the simplest example of a script that deals with errors is the call to
- "error" itself:
-
- error "Hello there."
- --! Error: Hello there. (-2700)
-
- Here, the result is not a returned value, but a message displayed in Toy
- Surprise's "execution error" dialog box. The number in parentheses is the
- actual runtime error code (-2700 is "user error", i.e. the script called the
- error statement without any particular error number -- we learn about particular
- error numbers later). Errors are also raised when an illegal operation is
- performed:
-
- {} + 234
- --! Can't make {} into a number.
-
- That may not seem like a big deal, but it comes in really handy when you're in
- the middle of a large computation and suddenly decide it's impossible to
- proceed. Calling error causes the script to be aborted, no matter where it is
- in the middle of computation. Let's put one in a subroutine:
-
- on fact(n)
- if n < 0
- error "Can't take fact of " & n & "."
- else if n = 0
- return 1
- else
- return n * fact(n - 1)
- end
- end
-
- fact(3)
- --> 6
-
- fact(-4)
- --! Error: Can't take fact of -4. (-2700)
-
- Now even if we embed this subroutine in another subroutine and an error is
- raised, we can abort:
-
- on factList(aList)
- set facts to {}
- repeat with x in aList
- set facts to facts & fact(x)
- end
- end
-
- factList({3, 4, 5, 5})
- --> {6, 24, 120, 120}
-
- factList({3, 4, 5, -5})
- --! Error: Can't take fact of -5. (-2700)
-
- factList({3, 4, 5, {}, -5})
- --! Can't make {} into a number.
-
- Now suppose we don't want to just jump back to the top-level with an error
- dialog if factList gets an error, but we want to return the empty list if any
- error occurred. This can be done by adding an error handler after the call to
- factList:
-
- factList({3, 4, 5, {}, -5})
- on error
- return {}
- end
- --> {}
-
- Note that these have to be executed as a unit (which they will be if you're
- using Toy Surprise). The error handler looks like a handler that occurs inter-
- mingled with other script statements. However, it's not a normal handler. It
- may be placed in the middle of scripts whereas other handlers cannot. For
- example, we could fold this empty-list-returning behavior into the factList
- function:
-
- on factList(aList)
- set facts to {}
- repeat with x in aList
- set facts to facts & fact(x)
- end
- on error
- return {}
- end
- end
-
- and achieve the same effect as the previous example (this is a little less
- flexible though, because factList will now never raise an error, and the
- script writer won't get a chance to deal with its errors in different ways).
-
- Error handler statements are also different from normal handlers in that they
- are only called when an error occurs. This error can occur anywhere in the
- statements that come before the error handler. If any of the preceeding
- statements are subroutine invocations (message sends) then the statements that
- they call will also be handled by the error handler, and the subroutines that
- they call, etc. This is called the "dynamic scope" of the computation.
-
- Let's change our factList example a little to have it insert zeros into the list
- for bogus input values:
-
- on factList(aList)
- set facts to {}
- repeat with x in aList
- set facts to facts & fact(x)
- on error
- set facts to facts & 0
- end
- end
- end
-
- factList({3, 4,-4, 5})
- --> {6, 24, 0, 120}
-
- Notice that the computation continues after the error handler just as if the
- statement has ated normally -- in this case at the end of the repeat loop which
- causes it to immediately loop back to the beginning.
-
- There are several parameters available to an error handler. For one, we can
- catch the error message in the direct parameter. Suppose we wanted to put
- the error message in the result list for each of the bogus input values. We
- could write:
-
- on factList(aList)
- set facts to {}
- repeat with x in aList
- set facts to facts & fact(x)
- on error msg
- set facts to facts & msg
- end
- end
- end
-
- factList({3, 4,-4, {}, 5})
- --> {6, 24, "Can't take fact of -4.", "Can't make {} into a number.", 120}
-
- We could compare against the error string if we wished to determine exactly
- which error had been raised and do something different in each case. However,
- there's a better way to do this. Errors can also be made to return error
- numbers. In the case of illegal operations, these numbers are predefined. In
- the case of user errors, the user may supply any number. Let's rewrite fact
- to return an error number too:
-
- on fact(n)
- if n < 0
- error "Can't take fact of " & n & "." number -234
- else if n = 0
- return 1
- else
- return n * fact(n - 1)
- end
- end
-
- We can catch the error in factList in a similar fashion. Here we return zero
- for the negative values to fact error, and insert the error message for all
- other errors:
-
- on factList(aList)
- set facts to {}
- repeat with x in aList
- set facts to facts & fact(x)
- on error msg number n
- if n = -234
- set facts to facts & 0
- else
- set facts to facts & msg
- end
- end
- end
- end
-
- factList({3, 4,-4, {}, 5})
- --> {6, 24, 0, "Can't make {} into a number.", 120}
-
- (Ok, so I said you'd never have to check error numbers again... Well, you only
- have to check them if you really care about them.)
-
- Besides the error message and error number, you can also get ahold of the
- error object and any partial result. The error object is the "object" that
- caused the error: an actor, an application object, etc. The error object can
- be captured in the "from" parameter:
-
- actor "fred"
- on foo()
- error number -23
- end
- end
-
- actor "bob"
- on foo()
- error number -23
- end
- end
-
- Here we define two actors, fred and bob that both respond to the same message,
- foo. Whichever one of them happens to receive the message at runtime will
- raise the same error, -23, but the error object will be bound to fred or bob,
- respectively:
-
- set x to actor "fred"
- tell x to foo()
- on error number n from errObj
- return {n, errObj}
- end
- --> {-23, actor "fred"}
-
- set x to actor "bob"
- tell x to foo()
- on error number n from errObj
- return {n, errObj}
- end
- --> {-23, actor "bob"}
-
- Similarly, when sending events to remote applications, we can catch the error
- object (the object specifier) that caused the error:
-
- tell application "Quill"
- close window 99
- end
- on error from f number n
- {n, f}
- end
- --> {-2705, window 99 of application "Quill"}
-
- Occasonally, when using whose-clauses the OSL will return partial results.
- These can be captured in the "partial results" parameter to an error handler:
-
- tell window 1 of application "Quill"
- get every word whose character 3 = "e"
- end
- on error number n from f partial result p
- {n, f, p}
- end
-
- If window 1 of application "Quill" contained the text "these are my words,"
- then the partial result, p, above would be the list {"these", "are"} -- the
- two words that succeeded before the third word didn't have a third character.
- (I'm not sure that this is working right for this release.) Script writers
- can of course use the partial result explicitly when an error is raised:
-
- on factList(aList)
- set facts to {}
- repeat with x in aList
- set facts to facts & fact(x)
- on error msg number n
- error msg number n partial result facts
- end
- end
- end
-
- factList({3, 4,-4, {}, 5})
- on error partial result p
- p
- end
- --> {6, 24}
-
- One final note. Whenever a remote application, applet, droplet, or system
- command returns an error number, an error is raised in AppleScript that may be
- caught with an error handler. For example, the "choose application" dialog
- returns a user canceled error if the user clicks "Cancel". This causes an error
- to be raised in AppleScript:
-
- choose application with prompt "What should I quit?"
- tell result
- quit
- end
- on error
- display dialog "Ok, I won't."
- end
-
- There's one exception: Display dialog itself doesn't return an error if the
- user clicks "cancel". Instead, it just returns a record with the label
- "button returned" which must be tested for the string "Cancel". In other words,
- you can't say this:
-
- display dialog "Are you there?"
- on error
- ... do error stuff...
- end
-
- You have to say this instead:
-
- display dialog "Are you there?"
- if button returned of result = "Cancel"
- ... do error stuff...
- end
-
- A future version of display dialog may behave differently. But... you can
- always write your own:
-
- on confirm(msg)
- display dialog msg
- if button returned of result = "Cancel"
- error number -128 (* userCanceledErr *)
- end
- end
-
- Happy error handling.
-